package com.cloudbroker.bcs.common.util;

import com.cloudbroker.bcs.common.annotation.HiddenIfEmpty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

public class ReflectionUtil {
    private static final Logger logger = LoggerFactory.getLogger(ReflectionUtil.class);
    private static final Class<?> ROOT_CLASS = Object.class;
    
    public static void makeAccessible(Method method , boolean acccessible) {
        if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass()
                .getModifiers())) && !method.isAccessible()) {
            method.setAccessible(acccessible);
        }
    }

    public static void makeAccessible(Method method) {
        makeAccessible(method , true);
    }
    
    public static void makeAccessible(Field field , boolean acccessible) {
        if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass()
                .getModifiers())) && !field.isAccessible()) {
            field.setAccessible(acccessible);
        }
    }

    public static String getClassName(Object obj) {
        if( obj == null )
            return StringUtil.EMPTY_STRING;
        return ValueUtil.getString(obj.getClass().getName());
    }

    public static String getClassSimpleName(Object obj) {
        if( obj == null )
            return StringUtil.EMPTY_STRING;
        return ValueUtil.getString(obj.getClass().getSimpleName());
    }

    public static void makeAccessible(Field field) {
        makeAccessible(field , true);
    }

    public static Map<String,Field> listAllFields(Class<?> clazz) {
        return listAllFields(null , clazz);
    }

    public static Map<String , Field> listAllFields(Map<String , Field> fieldMap , Class<?> clazz) {
        if( fieldMap == null ) {
            fieldMap = new HashMap<>();
        }
        if( clazz == ROOT_CLASS ) {
            return fieldMap;
        }
        for(Field field : clazz.getDeclaredFields() ) {
            if( !fieldMap.containsKey(field.getName()) ) {
                fieldMap.put(field.getName() , field);
            }
        }
        fieldMap.putAll(listAllFields(fieldMap, clazz.getSuperclass()));
        return fieldMap;
    }

    public static void setValueRough(Object obj, Field field , Object value) {
        boolean isAccessible = field.isAccessible();
        try {
            makeAccessible(field);
            field.set(obj , value);
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            makeAccessible(field , isAccessible);
        }
    }

    public static void setValue(Object obj , String fieldName , Object value) {
        try {
            PropertyDescriptor pd = new PropertyDescriptor(fieldName, obj.getClass());
            Method writeMethod;
            if( null != pd && null != (writeMethod = pd.getWriteMethod()) ) {
                writeMethod.invoke(obj, value);
            }
        }catch(Exception e) {
            logger.error(obj.getClass().getName() + " set " + fieldName +" value error" , e);
        }
    }

    public static Object getValue(Object obj, Field field) {
        return getValue(obj , field.getName());
    }

    public static Object getValueRough(Object obj, Field field) {
        boolean isAccessible = field.isAccessible();
        try {
            makeAccessible(field);
            return field.get(obj);
        }catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            makeAccessible(field , isAccessible);
        }
    }

    public static Object getValue(Object obj, String fieldName) {
        try {
            Method readMethod;
            PropertyDescriptor pd = new PropertyDescriptor(fieldName, obj.getClass());
            if( null != pd && null != (readMethod = pd.getReadMethod()) ) {
                return readMethod.invoke(obj);
            }
        }catch(Exception e) {
            logger.info(obj.getClass().getName() + " get " + fieldName +" value error" , e);
        }
        return null;
    }

    public static Field getField(Class<?> clazz , String fieldName) {
        try {
            return clazz.getField(fieldName);
        } catch (NoSuchFieldException | SecurityException e) {
            e.printStackTrace();
            System.out.println();
            return null;
        }
    }

    public static boolean isSubClass(Class<?> subClazz, Class<?> superClazz) {
        if( subClazz == null || subClazz == ROOT_CLASS) return false;
        if( subClazz == superClazz ) return true;
        else if( subClazz.getSuperclass() == superClazz ) return true;
        return isSubClass(subClazz.getSuperclass() , superClazz);
    }

    public static boolean hasField(Class<?> clazz , String fieldName) {
        if( clazz == null || clazz == ROOT_CLASS) return false;
        try {
            if (null != clazz.getDeclaredField(fieldName)) return true;
        }catch (Exception e) {
            logger.error("judge " + clazz.getName() + " has field : " + fieldName + " error " , e);
        }
        return hasField(clazz.getSuperclass() , fieldName);
    }

    public static void setValueIfExist(Object obj, String fieldName, Object value) {
        if( hasField(obj.getClass() , fieldName) ) {
            setValue(obj , fieldName , value);
        }
    }

    public static void hiddenEmptyAttrToJson(Object obj) {
        if( null != obj ) {
            Map<String,Field> fieldMap = listAllFields(obj.getClass());
            for(Map.Entry<String,Field> entry : fieldMap.entrySet()) {
                Field field = entry.getValue();
                if( null != field && field.isAnnotationPresent(HiddenIfEmpty.class) &&
                        ValueUtil.getString(getValue(obj , field)).isEmpty() ) {
                    setValueRough(obj , field , null);
                }
                if( "data_list".equals(entry.getKey()) && getValue(obj, field) == null ) {
                    setValueRough(obj , field , new ArrayList<>());
                }
            }
        }
    }
}
